/*
 * ModelCC, distributed under ModelCC Shared Software License, www.modelcc.org
 */

package org.modelcc.language.metamodel;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.modelcc.lexer.recognizer.PatternRecognizer;
import org.modelcc.metamodel.Model;

/**
 * Language model.
 * 
 * @author Luis Quesada (lquesada@modelcc.org) & Fernando Berzal (berzal@modelcc.org)
 */
public final class LanguageModel extends Model<LanguageElement> implements Serializable,Cloneable 
{
    /**
     * The start element of this model.
     */
    private LanguageElement start;

    /**
     * Delimiters of this model.
     */
    private Set<PatternRecognizer> delimiters;

    /**
     * ModelElement precedence map.
     */
    private Map<LanguageElement,Set<LanguageElement>> precedences;


    /**
     * ModelElement default element map.
     */
    private Map<LanguageElement, Set<LanguageElement>> defaultElement;

    /**
     * Constructor.
     * @param elements the set of elements of this model.
     * @param start the start element of this model
     * @param delimiters the delimiters of this model
     * @param precedences the element precedence map
     * @param subelements the element subclass map
     * @param superelements the element superclass map
     * @param classToElement the class to element map
     */
    public LanguageModel(Set<LanguageElement> elements,LanguageElement start,Set<PatternRecognizer> delimiters,Map<LanguageElement,Set<LanguageElement>> precedences,Map<LanguageElement,Set<LanguageElement>> subelements,Map<LanguageElement,LanguageElement> superelements,Map<Class,LanguageElement> classToElement,Map<LanguageElement, Set<LanguageElement>> defaultElement) 
    {
    	super(elements,subelements,superelements,classToElement);
    	
    	// Language model specifics
        this.start = start;
        this.delimiters = delimiters;
        this.precedences = precedences;
        this.defaultElement = defaultElement;
    }


    /**
     * @return the start element of this model
     */
    public LanguageElement getStart() {
        return start;
    }

    /**
     * @return the delimiters of this model
     */
    public Set<PatternRecognizer> getDelimiters() {
        return Collections.unmodifiableSet(delimiters);
    }

    /**
     * @return the element precedences map
     */
    public Map<LanguageElement, Set<LanguageElement>> getPrecedences() {
        return Collections.unmodifiableMap(precedences);
    }

    /**
     * @return the defaultElement
     */
    public Map<LanguageElement, Set<LanguageElement>> getDefaultElement() {
        return Collections.unmodifiableMap(defaultElement);
    }

    
    @Override
	public LanguageModel clone(){
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            ByteArrayOutputStream bOs = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bOs);
            oos.writeObject(this);
            ois = new ObjectInputStream(new ByteArrayInputStream(bOs.toByteArray()));
        	LanguageModel ret = (LanguageModel)ois.readObject();
            return ret;

        } catch (Exception e) {
        	e.printStackTrace();
            return null;
        }finally {
            if (oos != null)
                try {
                    oos.close();
                } catch (IOException e) {

                }
            if (ois != null)
                try {
                    ois.close();
                } catch (IOException e) {

                }
        }
    }

	public void addPrecedence(LanguageElement me, LanguageElement other) {
		Set<LanguageElement> precs1 = precedences.get(me);
		if (precs1 == null) {
			precs1 = new HashSet<LanguageElement>();
			precedences.put(me,precs1);
		}
		precs1.add(other);
		Set<LanguageElement> precs2 = precedences.get(other);
		if (precs2 != null) {
			precs2.remove(me);
			if (precs2.isEmpty())
				precedences.remove(other);
		}
	}

    public boolean checkPrecedences() 
    {
    	Set<LanguageElement> pool = new HashSet<LanguageElement>();
    	pool.addAll(getElements());

    	// Token specifications preceded by any token specification in the pool.
    	Set<LanguageElement> precededs;

    	Iterator<LanguageElement> ite;
    	LanguageElement ts;
    	Set<LanguageElement> pset;

    	boolean found; // Whether any new token specification has been added to the sorted list.



    	while (!pool.isEmpty()) {

    		found = false;

    		// Update precededs list.
    		precededs = new HashSet<LanguageElement>();
    		for (ite = pool.iterator();ite.hasNext();) {
    			ts = ite.next();
    			pset = precedences.get(ts);
    			if (pset != null) {
    				precededs.addAll(pset);
    			}
    		}

    		// Adds news unprecededs.
    		for (ite = pool.iterator();ite.hasNext();) {
    			ts = ite.next();
    			if (!precededs.contains(ts)) {
    				ite.remove();
    				found = true;
    			}
    		}

    		if (!found) {
    			// List of conflicting elements.
    			// String list = new String();
    			// for (ite = pool.iterator();ite.hasNext();)
    			//    list += " "+ite.next().getElementClass().getCanonicalName();
    			return false;
    		}
    	}

    	return true;
    }
}
